package com.legendshop.oa.security;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;

import com.legendshop.oa.model.UserEntity;


/**
 * 验证器的基类
 */
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean,
        MessageSourceAware {

    /** The logger. */
    protected final Log logger = LogFactory.getLog(getClass());

    /** The messages. */
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    
    /** The user cache. */
    private UserCache userCache = new NullUserCache();
    
    /** The force principal as string. */
    private boolean forcePrincipalAsString = false;
    
    /** The hide user not found exceptions. */
    protected boolean hideUserNotFoundExceptions = true;
    
    /** The pre authentication checks. */
    private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    
    /** The post authentication checks. */
    private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    
    /** The authorities mapper. */
    protected GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    /**
     * 额外的检查项目
     */
    protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
    		Authentication authentication)
        throws AuthenticationException;

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public final void afterPropertiesSet() throws Exception {
        Assert.notNull(this.userCache, "A user cache must be set");
        Assert.notNull(this.messages, "A message source must be set");
        doAfterPropertiesSet();
    }

    /* (non-Javadoc)
     * @see org.springframework.security.authentication.AuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
     */
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                user = retrieveUser(username, authentication);
            } catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                } else {
                    throw notFound;
                }
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user,  authentication);
        } catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username, authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,authentication);
            } else {
                throw exception;
            }
        }

        postAuthenticationChecks.check(user);

        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    /**
    *
    * @return 成功建立的token
    */
   protected abstract Authentication createSuccessAuthentication(Object principal, Authentication authentication,  UserDetails user);
   
    /**
     * Do after properties set.
     *
     * @throws Exception the exception
     */
    protected void doAfterPropertiesSet() throws Exception {}

    /**
     * Gets the user cache.
     *
     * @return the user cache
     */
    public UserCache getUserCache() {
        return userCache;
    }

    /**
     * Checks if is force principal as string.
     *
     * @return true, if is force principal as string
     */
    public boolean isForcePrincipalAsString() {
        return forcePrincipalAsString;
    }

    /**
     * Checks if is hide user not found exceptions.
     *
     * @return true, if is hide user not found exceptions
     */
    public boolean isHideUserNotFoundExceptions() {
        return hideUserNotFoundExceptions;
    }

    /**
     * 获取用户信息
     */
    protected abstract UserDetails retrieveUser(String username, Authentication authentication)
        throws AuthenticationException;

    /**
     * Sets the force principal as string.
     *
     * @param forcePrincipalAsString the new force principal as string
     */
    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
        this.forcePrincipalAsString = forcePrincipalAsString;
    }

    /**
     * By default the <code>AbstractUserDetailsAuthenticationProvider</code> throws a
     * <code>BadCredentialsException</code> if a username is not found or the password is incorrect. Setting this
     * property to <code>false</code> will cause <code>UsernameNotFoundException</code>s to be thrown instead for the
     * former. Note this is considered less secure than throwing <code>BadCredentialsException</code> for both
     * exceptions.
     *
     * @param hideUserNotFoundExceptions set to <code>false</code> if you wish <code>UsernameNotFoundException</code>s
     *        to be thrown instead of the non-specific <code>BadCredentialsException</code> (defaults to
     *        <code>true</code>)
     */
    public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
        this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
    }

    /* (non-Javadoc)
     * @see org.springframework.context.MessageSourceAware#setMessageSource(org.springframework.context.MessageSource)
     */
    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    /**
     * Sets the user cache.
     *
     * @param userCache the new user cache
     */
    public void setUserCache(UserCache userCache) {
        this.userCache = userCache;
    }

    /**
     * Gets the pre authentication checks.
     *
     * @return the pre authentication checks
     */
    protected UserDetailsChecker getPreAuthenticationChecks() {
        return preAuthenticationChecks;
    }

    /**
     * Sets the policy will be used to verify the status of the loaded <tt>UserDetails</tt> <em>before</em>
     * validation of the credentials takes place.
     *
     * @param preAuthenticationChecks strategy to be invoked prior to authentication.
     */
    public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
        this.preAuthenticationChecks = preAuthenticationChecks;
    }

    /**
     * Gets the post authentication checks.
     *
     * @return the post authentication checks
     */
    protected UserDetailsChecker getPostAuthenticationChecks() {
        return postAuthenticationChecks;
    }

    /**
     * Sets the post authentication checks.
     *
     * @param postAuthenticationChecks the new post authentication checks
     */
    public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
        this.postAuthenticationChecks = postAuthenticationChecks;
    }

    /**
     * Sets the authorities mapper.
     *
     * @param authoritiesMapper the new authorities mapper
     */
    public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
        this.authoritiesMapper = authoritiesMapper;
    }

    /**
     * The Class DefaultPreAuthenticationChecks.
     */
    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {

        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                logger.debug("User account is locked");
                throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
            }

            if (!user.isEnabled()) {
                logger.debug("User account is disabled");
                throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
            }

            if (!user.isAccountNonExpired()) {
                logger.debug("User account is expired");
                throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",  "User account has expired"));
            }
        }
    }

    /**
     * The Class DefaultPostAuthenticationChecks.
     */
    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                logger.debug("User account credentials have expired");
                throw new CredentialsExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
            }
        }
    }
    
    
    
    /**
     * 根据商家和用户转化为用户对象
     */
	protected User createUser(UserEntity user) {
		User minuser = new UserDetail(user.getId(), user.getName(), user.getPassword(), user.getPortraitPic(), 
				getBoolean(user.getEnabled()), true, true, true, user.getRoles(), user.getFunctions(),
				user.getDeptId(),user.getRealName(),user.getPassChangeDate(), user.getChangePass());
		return minuser;
	}
	

	/**
	 * Gets the boolean.
	 * 
	 * @param b
	 *            the b
	 * @return the boolean
	 */
	private boolean getBoolean(String b) {
		return "1".endsWith(b) ? true : false;
	}

}